/** ResourceLoader.java
 *
 * Provides Multimedia resource loading services.
 * Multimedia resource may be pictures/sound/music files.
 *
 * @author Somanath
 * @version 1.1 (last updated 15/9/02)
 *
 * class Dependencies: CImage    			// A resouce to be loaded.
 *                     GamePackException    // thrown when resource loading fails.
 
 	Class - ResourceLoader
	Abstracs - ResourceLoading
	Compile time class Dependency - Application, CImage, Sound, Music (each of these classes have no compile time dependencies)
	Known Dependents - None
	Assumptions - 
	Purpose - Utility Class to aid with loading Images/Audio files and XML files.
	Other Info - Since ResouceLoader internally dependends on Application class, Any class planning to use this utility class should be aware of the Limitation of Application class.
 
 */

package gamepack;

import java.util.Vector;
import java.awt.Image;
import java.awt.MediaTracker;
import java.applet.AudioClip;
import java.net.URL;
import java.io.*;  // TODO : put specific class here.
import java.util.*;
import java.awt.Polygon;
import java.awt.*;

// Required for XMLReader
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.File;
import java.io.IOException;
import org.w3c.dom.Document;
import org.w3c.dom.*;
import org.w3c.dom.DOMException;
import java.awt.Rectangle;
import java.util.*;

// Helper class to build relative CImages ie. CImages that are defined wrt to a Affine transform on another CImage.
// It contains only meta data required to construct a RelativeCImageData
// This class is very specific helper class used by decodeImageData() method.
class RelativeCImageData {
	int i,j;  // The array subscript of this Relative CImage  // Resource.cimg[i][j];
	String s; // The string which contains information to build the relative cimage.
	
	
	boolean isRelative;
	int m,n;  // The array subscript of the Resource.cimg[m][n] to which this RelativeCImage is relative. 
			  // m and n are present in the string s;
	String transform; // The transformation that needs to be applied to create this RelativeCImage Eg. rotate,flip etc.
	int additionalData;     // Additional information required to apply the transformation. Depends on 'transform'  
							   // Eg. if transform == 'rotate' then addiontalData is the angle of rotation in degrees. Say 45 degree.
	
	CImage cimg; // One the relative cimg is built it is stored here.
	
	RelativeCImageData(int i_,int j_,String s_) {
		i = i_;
		j = j_;
		s = s_;		
	}
	
	boolean isRelative() { // parses the string s to see if this is relative image.
		return isRelative;
	}
	
	// parses string (attribute s) and populates isRelative, m,n, transform, additionalData.
	void parse() {
		// The string is expected to be of the form (See 2nd line within <Image> tag) - 
		// <Image name="player1,left">T1_LEFT.GIF,  22, 13          , RECTANGLE, 9,0,26,26 </Image>
		// <Image name="player1,right"> RELATIVE(0:0) ,   rotate,        180 </Image>  
		//                                |       | |         |            |
		//                          (is_Relative)(m:n), transformation, additionalData
		// Note the string starts with string Relative and has 3 parts.

         String data = s.trim();
         String[] arr = data.split(",");
         int numParts = arr.length;
         //System.out.println("split into " + arr.length + " parts");
 		 isRelative = arr[0].toUpperCase().startsWith("RELATIVE") ? true : false;
		 if( !isRelative) return; // No point parsing furthur.

         if( numParts != 3 ) {
             throw new RuntimeException("Incorrect number of arguments in <IMAGE> tag for Relative Image. Expected 3 parts, Found " + numParts);
         }
		 
		 int openBrace = arr[0].indexOf('(');
		 int closeBrace = arr[0].indexOf(')');
		 if( openBrace == -1 || closeBrace == -1 ) {
			throw new RuntimeException("Invalid arguments in <IMAGE> tag for Relative Image. Expected RELATIVE(m,n). ");
		 }		 
		 try {
			 String mn = arr[0].substring( openBrace+1, closeBrace  ); 		 
			 String[] mnparts = mn.split(":");
			 m = Integer.parseInt( mnparts[0].trim() );
			 n = Integer.parseInt( mnparts[1].trim() );

			 transform = arr[1].trim();
			 additionalData = Integer.parseInt( arr[2].trim() );		
		 }
		 catch ( NumberFormatException e ) {
			throw new RuntimeException("NumberFormatException while parsing <Image> tag of relative image. Parse String = \"" + 
			s + "\" " + e.getMessage() );
		 }
	}	
}

class Decoder {

/* 
ResourceLoader and CImage have differences in the way they interpret data.

1. * ResouceLoader assumes that the center of the image specified in xml is +ve integer.
   * CImage assumes the center of image to be the same but with its sign inversed.

2. * ResourceLoader assumes that the Collision rect/polygon specified in xml is wrt top left corner (0,0) of the image.
   * CImage assumes the collision rect to be defined relative to the center/anchor point of the image. 
	
3. * ResourceLoader is the interface of the artist to the sprites. Interprets xml data which is stored in a manner 
	 friendly to artist and then constructs a CImage object out of it. 
   * CImage is the part of the Sprite. It contains Image/collision information stored in a manner suitable for programming.
	
*/


	// Does the real job of making CImage 
    public static CImage makeCImage(String data) {

         int x=0,y=0;
         int x1=0,y1=0,w1=0,h1=0;
         data = data.trim();
         String[] arr = data.split(",");
         int numParts = arr.length;
         //System.out.println("split into " + arr.length + " parts");
         if( !(numParts == 1 || numParts == 3 || numParts > 4 ) ) {
             throw new RuntimeException("Incorrect number of arguments in <IMAGE> tag");
         }
         // get CImage from arr[0]
         String cimageStr = arr[0].trim();
         //System.out.println(" cimageStr = " + cimageStr );
         Image img = ResourceLoader.getImage( cimageStr);
         int width = img.getWidth(null);
         int height = img.getHeight(null);

         if( numParts == 1 ) {
            //  calc with and height from image
            x = - width/2; y = - height/2;
            x1 = x;        y1 = y;
            w1 = width;    h1 = height;
         }
         if( numParts == 3 ) {
            x = Integer.parseInt(arr[1].trim());
            y = Integer.parseInt(arr[2].trim());
            x1 = x;        y1 = y;
            w1 = width;    h1 = height;
         }
		 
		 if( numParts > 4 ) {
			String boundingShape = boundingShape = ( arr[3].trim() ); // The 4th word 

			 if( boundingShape.toUpperCase().equals("RECTANGLE") ) {
				if ( numParts != 8 ) {
					throw new RuntimeException("Incorrect number of arguments with <IMAGE> tag : 4 Arguments expected after RECTANGLE ");
				}
				x = - (Integer.parseInt(arr[1].trim()) ); // ResourceLoader expects it as a +ve number, but CImage stores it as a -ve number.
				y = - (Integer.parseInt(arr[2].trim()) );
				
				x1 = ( Integer.parseInt(arr[4].trim()) ) + x ;  // Translate from 0,0 co-ordinate to relative to x,y
				y1 = ( Integer.parseInt(arr[5].trim()) ) + y ;
				w1 = Integer.parseInt(arr[6].trim());
				h1 = Integer.parseInt(arr[7].trim());
				
			 } else if( boundingShape.toUpperCase().equals("POLYGON")  ) {
				x = - (Integer.parseInt(arr[1].trim()) ); // ResourceLoader expects it as a +ve number, but CImage stores it as a -ve number.
				y = - (Integer.parseInt(arr[2].trim()) );
			 
				int npoints = Integer.parseInt(arr[4].trim()) ;  // Number of sides of the polygon given by 5th part			
				int partsExpected = 5 + npoints*2;
				
				if( numParts != partsExpected ) {
					throw new RuntimeException("Incorrect number of arguments with <IMAGE> tag : Expected "
					+ partsExpected + "argument (POLYGON), found "+ numParts + " arguments ");					
				}
				int[] xpoints = new int[npoints];
				int[] ypoints = new int[npoints];

				for( int i=0;i<npoints;i++ ) { // 				
					xpoints[i] = Integer.parseInt(arr[5  +i*2].trim());    // Polygon's 1st point.x start at 6th part
					ypoints[i] = Integer.parseInt(arr[5+1+i*2].trim());    // Polygon's 1st point.x start at 7th part
				}						 				
				Polygon p = new Polygon ( xpoints, ypoints, npoints );
				p.translate( x , y ); 	// CImage expects the polygon to be defined relative to center of image.
				CImage cimg = new CImage(img, x, y, p, null);		
				return cimg;
			 
			 } else {
				throw new RuntimeException("Incorrect parameter in <IMAGE> tag : The 4th word should be RECTANGLE or POLYGON");
			 }
			
			
		 }
		 
         Rectangle rect = new Rectangle(x1,y1,w1,h1);
         CImage cimg = new CImage(img, x, y,rect);

         return  cimg;
    }
		
public static CImage[][] decodeImageData( Vector<NodeInfo> imgdata, Vector<NodeInfo> imgsetdata ) {
       int totalImages = imgdata.size();
       int numImgSets = 1; // By default there is only 1 image set (unless mentioned in xml file)
       int sum = 0;
       int[] dimen = null;

       if( imgsetdata.size() == 1 ) {
           String s = imgsetdata.get(0).data;
           s = s.trim();
           String[] arr = s.split(":");
           numImgSets = arr.length;
           dimen = new int[ numImgSets ];
           for( int i=0;i< numImgSets; i++ ) {
                dimen[i] = Integer.parseInt( arr[i].trim() );  // trim required when string is "4 "
                sum += dimen[i];
           }
       } else {
         dimen = new int[ 1 ];
         dimen[0] = totalImages;
       }

       if( numImgSets > 1 && sum != totalImages ) {
           throw new RuntimeException("Incorrect Format in XML file for ImageSet tag");
       }

	   // Lets build retImg[][] and arrRCI[][] for all images.
       int count = 0;
       CImage[][] retImg = new CImage[numImgSets][];
	   RelativeCImageData[][] arrRCI = new RelativeCImageData[numImgSets][];
       for( int i =0 ; i< numImgSets; i++ ) {
            retImg[i] = new CImage[ dimen[i] ];
			arrRCI[i] = new RelativeCImageData[ dimen[i] ];
			
            for( int j=0; j< retImg[i].length; j++ ) {
                 String dataStr = imgdata.get(count).data;			
				 
				 arrRCI[i][j] = new RelativeCImageData(i,j,dataStr);
				 arrRCI[i][j].parse();
				 // If dataStr is a relative CImage ie. defined in terms of a Affine transform on another CImage, we keep it as null for now.
				 if( !arrRCI[i][j].isRelative() ) {
					 retImg[i][j] = makeCImage( dataStr );					 
				 }
				 count++;
            }
       }
	   
	   // retImg[i][j] will be null for Relative CImages. So building relative images.
		for( int i=0; i< arrRCI.length; i++) {
			for( int j=0;j<arrRCI[i].length; j++) {
				buildCImage( i,j , arrRCI, retImg ); // all relative image should get built here (recursively)
			}
		}
		
       return retImg;
}

public static void buildCImage(int i,int j, RelativeCImageData[][] arrRCI, CImage[][] retImg) {
	if( retImg[i][j] != null ) return;  // If cimg[i][j] already exists just return. (leaf of recursion)	
			RelativeCImageData rcid = arrRCI[i][j];
			
			CImage orig_cimg = retImg[rcid.m][rcid.n]; // The image wrt to which the relative cimage needs to be constructed.			
			if( orig_cimg == null ) {
				buildCImage( rcid.m, rcid.n , arrRCI, retImg ); // recursive call.
				orig_cimg = retImg[rcid.m][rcid.n];
			} 
			CImage rci_cimg = CImageUtil.affineTransform( rcid.transform, orig_cimg, rcid.additionalData ); // Build the Relative CImage			
			
			retImg[rcid.i][rcid.j] = rci_cimg;
			return;
}

public static Sound[] decodeSoundData( Vector<NodeInfo> snddata ) {
       int numSounds = snddata.size();
       Sound[] retSound = new Sound[ numSounds ];

       for(int i=0;i<numSounds;i++) {
           NodeInfo ni = snddata.get(i);
           retSound[i] = ResourceLoader.getSound( ni.data.trim() );
       }
       return retSound;
}

public static Music[] decodeMusicData( Vector<NodeInfo> musdata ) {
       int numMusic = musdata.size();
       Music[] retMusic = new Music[ numMusic ];

       for(int i=0;i<numMusic;i++) {
           NodeInfo ni = musdata.get(i);
           retMusic[i] = ResourceLoader.getMusic( ni.data.trim() );
       }
       return retMusic;
}

public static Map<String,String> decodeOtherData( Vector<NodeInfo> otherdata ) {
       Map<String,String> retMap = new HashMap<String,String>();
       int numOtherdata = otherdata.size();

       for(int i=0;i<numOtherdata;i++) {
           NodeInfo ni = otherdata.get(i);

           String attribName = ni.attribs.get("name");
           if( attribName != null && attribName != "" ) {
               retMap.put( attribName , ni.data );
           }
       }
       return retMap;
}

public static Map<String,RoomData> decodeRoomData( Vector<NodeInfo> otherdata ) {
	//System.out.println("Entered decodeRoomData()");
       Map<String,RoomData> retMap = new HashMap<String,RoomData>();
       int numOtherdata = otherdata.size();
	   //System.out.println(" Entered decodeRoomData - Number of nodes " + otherdata.size() );

       for(int i=0;i<numOtherdata;i++) {
           NodeInfo ni = otherdata.get(i);
           String attribName = ni.attribs.get("name");
	   
           if( attribName != null && attribName != "" ) {
		       RoomData rd = ResourceLoader.getRoomData( ni.data.trim() );
		//	   System.out.println(rd.toString() );
               retMap.put( attribName , rd );			   
           }
       }
	   	//System.out.println("Exited decodeRoomData()");
       return retMap;
}


public static CImage getPssCImage(String indexString, CImage[][] cimg) {
	// indexString is of format "a,b" where a and b are numbers
	// which point to CImage given by cimg[a][b]
	CImage retCImage = null;
	int a,b;
	String[] parts = indexString.split(",");
	try {	
		if( parts.length < 2 ) {
			throw new RuntimeException("Check config xml file for <SpawnString \"cimage=a,b\"> tag. Missing \",\" in \""+ indexString +"\""); 
		}
		a = Integer.parseInt( parts[0] );
		b = Integer.parseInt( parts[1] );
		retCImage = cimg[a][b];		
	} 
	catch( ArrayIndexOutOfBoundsException e ) {
	throw new RuntimeException("Check config xml file for <SpawnString \"cimage="+indexString	+"\"> tag. Check cimage value for correctness. ArrayIndexOutOfBounds " + e.getMessage());
	}
	catch ( NumberFormatException e ) {
		throw new RuntimeException("Check config xml file for <SpawnString \"cimage=a,b\"> tag. a,b must be integers but a=("+parts[0]+") and b=("+parts[1]+") are expected to be numbers. " + e.getMessage() );
	}
	return retCImage;		
}

// Assumes decodeCImage has been called.
public static PreferredSpawnString[] decodeSpawnString( Vector<NodeInfo> spawnData, CImage[][] cimg ) {

       int numSpawns = spawnData.size();
       PreferredSpawnString[] retPSS = new PreferredSpawnString[ numSpawns ];

       for(int i=0;i<numSpawns;i++) {
			NodeInfo ni = spawnData.get(i);
			retPSS[i] = new PreferredSpawnString();
			retPSS[i].spawnString = ni.data.trim();	

           String attribName = ni.attribs.get("cimage");
           if( attribName != null && attribName != "" ) {
               //retMap.put( attribName , ni.data );
			   // attribName will contain a string like "0,0" which actually refers to cimg[0][0] but it may
			   // also contain a string which starts with @. Eg. "@ledge" which means that there is no cimage associated 
			   // with the spawnstring, instead use alternate text "ledge" or create a image containing alternate text for use.
			   if( attribName.startsWith("@") ) {
					retPSS[i].cimg = createCImageFromString( attribName );
			   } else {			   
					retPSS[i].cimg = getPssCImage(attribName,cimg); //-------------------
			   }
			   retPSS[i].indexStr = attribName;
           }
		   attribName = ni.attribs.get("name");
           if( attribName == null  ) {
				attribName = "Default";
           }
			   retPSS[i].name = attribName;
			   retPSS[i].pos = i;			
       }
       return retPSS;
}

	static CImage createCImageFromString(String s) {	// TODO: Optimize the code to avoid creating 2 images.
		s = s.substring(1); // get rid of the @ sign
		Image i = Application.createImage(10,10);
		Graphics g = i.getGraphics();
		int h = g.getFontMetrics().getHeight();
		int w = g.getFontMetrics().stringWidth( s );
		i = Application.createImage( w+5,h+5 );	
		g = i.getGraphics();
		g.drawString(s,0,h);
		return new CImage(i); // create a image which contains the string s.
	}
	
} // end of class Decoder

// Note - Not sure if ResouceLoader is properly made thread safe. If your application uses AWT, which internally spawns threads and one of these
// AWT components call ResouceLoader, then there have been some issues, was not able to investigate the thread safety as the problem dissappears
// after adding debugging printfs.
public class ResourceLoader {

    // ToDo: Configurable items: should be configurable thru gamepack.xml
    static public String rootDir = "resource/";

    // The Directory which contains a single xml file
    // describing all Sprite resources. This directory
    // resides under "resource" directory
    static String commonResourceDir = "common/";

        // First search for the resource in the common
    // resource directory then search in Sprite specific
    // resource directory.
	static boolean firstSearchCommonResource = false;

    static Map<String,Resource> spriteResourceCache = new HashMap<String,Resource>();
	
    public static Resource getResource(String spriteName) {
           // see if the resource exists in cache if yes return from cache
           // else load from xml file and update cache before returning.
           Resource sr = null;
           if( spriteResourceCache.containsKey( spriteName ) ) {
               sr = spriteResourceCache.get( spriteName );
           } else {
               sr = getResourceFromFile(spriteName);
			   Game.println("ResourceLoaded for " + spriteName + "-------------------");
               // We need to update Environment.familyOfFamilies to include this.
               spriteResourceCache.put( spriteName, sr );
           }
           return sr;
    }

    public static void removeResource(String spriteName) {
           if( spriteResourceCache.containsKey( spriteName ) ) {
               spriteResourceCache.remove( spriteName );
           }
    }
	
	public static String toPath(String fullsprName) {   
		// Expected output of this helper method - 
		// say fullyQualifiedSpriteName = "tankattack.sprites.Tank
		// then this function should return "tankattack/sprites/tankattack.sprites.Tank"
			
		String packageName = fullsprName.substring( 0 , fullsprName.lastIndexOf(".")+1 ); // packageName = tankattack.sprites
		String tmp = packageName.replace('.' , '/');  // tmp = tankattack/sprites/		
		return tmp + fullsprName;
	}

	static Object syncObj = new Object();
    // where spriteName is the fully qualified class name for the Sprite
    public static Resource getResourceFromFile(String spriteName) {
		synchronized (syncObj) { // half hearted attempt at making this method thread safe.
		//System.out.println("Entered Sync block +++++++++++++++++++++++++++++++++++++++ ");
   /**
    *   1. Get the SRD that needs to be searched first based on flag
    *      get the name of the xml file that needs to be looked into
    *   2. Search the xml file and retrieve map<String,String> for Image
    *   3. |||'y get data for Sound and Music
    *   4. Use the data to create Resource and return.
    *
    */
	try {
		String origRootDir = rootDir;
          String xmlFile = null;
          if( !firstSearchCommonResource ) {
				xmlFile = toPath(spriteName) + "/" + spriteName + ".xml";
				rootDir = toPath(spriteName) + "/";
          } else {
             xmlFile = rootDir + commonResourceDir + "/" + commonResourceDir + ".xml";
             rootDir = rootDir + commonResourceDir;
          }
				//System.out.println("xmlFile=(" + xmlFile + ")" );
				//System.out.println("rootDir=(" + rootDir + ")" );
			
          String tag = "Resource:name=" + spriteName;
          String attrib = "name";
          Vector<NodeInfo> imgdata = XMLReader.getNodeInfo(xmlFile,tag,"Image");
          Vector<NodeInfo> imgsetdata = XMLReader.getNodeInfo(xmlFile,tag,"ImageSet");
          Vector<NodeInfo> snddata = XMLReader.getNodeInfo(xmlFile,tag,"Sound");
          Vector<NodeInfo> musdata = XMLReader.getNodeInfo(xmlFile,tag,"Music");
          Vector<NodeInfo> otherdata = XMLReader.getNodeInfo(xmlFile,tag,"Data");
		  Vector<NodeInfo> spawndata = XMLReader.getNodeInfo(xmlFile,tag,"SpawnString");
		  Vector<NodeInfo> roomdata = XMLReader.getNodeInfo(xmlFile,tag,"RoomData");

          CImage[][] cimg_arr = Decoder.decodeImageData( imgdata , imgsetdata );
          Sound[]  snd_arr = Decoder.decodeSoundData( snddata );
          Music[] mus_arr = Decoder.decodeMusicData( musdata );
          Map<String,String> data_map = Decoder.decodeOtherData( otherdata );
		  PreferredSpawnString[] pss_arr = Decoder.decodeSpawnString( spawndata , cimg_arr);
		  Map<String,RoomData> room_map = Decoder.decodeRoomData( roomdata );		  
          Resource sr = new Resource();
          sr.img = cimg_arr;
          sr.sound = snd_arr;
          sr.music = mus_arr;
          sr.data = data_map;
          sr.name = spriteName;
		  sr.pss = pss_arr;
		  sr.roomData = room_map;

          rootDir = origRootDir;
			
		  //System.out.println("Exiting Sync block -----------------------------------");	
          return sr;
		}catch( RuntimeException e ) {
			throw new RuntimeException( e.getClass().getName() + " Error while loading resource " + spriteName + "  e.getMessage() = " + e.getMessage() );
		}
	  }
    }


	public static CImage[][] getCImage(String[][] s) {
		CImage[][] cimg = new CImage[s.length][];
		for(int i=0;i<s.length;i++)
			cimg[i] = getCImage(s[i]);
		return cimg;
	}

	public static CImage[] getCImage(String[] s) {
		CImage[] cimg = new CImage[s.length];
		for(int i=0;i<s.length;i++) {
			Image img = Application.getImage(rootDir + s[i]);
			if(img == null)
				throw new GamePackException("Application.getImage(\""+s[i]+"\")");
			MediaTracker mt = new MediaTracker(Application.c);
			mt.addImage(img,i);
			try { mt.waitForID(i); }
			catch( InterruptedException e ) {
				throw new GamePackException("MediaTracker.waitForID()" + s[i]);
			}

			if( mt.isErrorID(i) )
				throw new GamePackException("MediaTracker Error: " + s[i]);
			cimg[i] = new CImage(img);
		}
		return cimg;
	}


	public static Image[] getImage(String[] s) {
		Image[] img = new Image[s.length];
		for(int i=0;i<s.length;i++) {
			if( s[i] == "" ) { img[i] = null; continue; }

			img[i] = Application.getImage(rootDir + s[i]);
			MediaTracker mt = new MediaTracker(Application.c);
			mt.addImage(img[i],i);
			try { mt.waitForID(i); }
			catch( InterruptedException e ) {
				throw new GamePackException("MediaTracker.waitForID() " + s[i] );
			}
			if( mt.isErrorID(i) )
				throw new GamePackException("MediaTracker.isErrorID() " + s[i] );
		}
		return img;
	}

	public static Image getImage(String s) {
			
			Image img = Application.getImage(rootDir + s);
			MediaTracker mt = new MediaTracker(Application.c);
			mt.addImage(img,0);
			try { mt.waitForID(0); }
			catch( InterruptedException e ) {
				throw new GamePackException("MediaTracker.waitForID() " + s );
			}
			if( mt.isErrorID(0) )
				throw new GamePackException("MediaTracker.isErrorID() " + s );
			return img;
	}

	public static Image getImage(URL url) {
			Image img = Application.getImage(url);
			MediaTracker mt = new MediaTracker(Application.c);
			mt.addImage(img,0);
			try { mt.waitForID(0); }
			catch( InterruptedException e ) {
				throw new GamePackException("MediaTracker.waitForID() " + url );
			}
			if( mt.isErrorID(0) )
				throw new GamePackException("MediaTracker.isErrorID() " + url );
			return img;
	}
		

	public static int getWave(String wavFile) {
		try {
			if( Application.type == Application.APPLET ) return 0;
			URL url = Application.classLoader.getResource(rootDir + wavFile);
			return WaveEngine.load(url);
		}
		catch(Exception e) { throw new RuntimeException(e.getMessage()); }
	}

	public static int getMidi(String midiFile) {
		try {
			if( Application.type == Application.APPLET ) return 0;
			URL url = Application.classLoader.getResource(rootDir + midiFile);
			if( url == null ) {				
				throw new RuntimeException("getMidi("+midiFile+") Unable to getResource("+rootDir + midiFile+") Pls check configuration file");
			}
			return MusicEngine.load(url);
		}
		catch(Exception e) { throw new RuntimeException("getMidi("+midiFile+") throwing :" + e.getClass().getName() + " Message : " + e.getMessage()); }
	}

	public static int[] getWave(String[] s) {
		int[] arr = new int[s.length];
		for(int i=0;i<s.length;i++)
			arr[i] = getWave(s[i]);
		return arr;
	}

	public static int[] getMidi(String[] s) {
		int[] arr = new int[s.length];
		for(int i=0;i<s.length;i++)
			arr[i] = getMidi(s[i]);
		return arr;
	}

	public static Sound getSound(String s) {
	       return new Sound( getWave(s) );
	}
	public static Music getMusic(String s) {
	       return new Music( getMidi(s) );
	}
    public static Sound[] getSound(String[] s) {
        Sound[] arr = new Sound[s.length];
		for(int i=0;i<s.length;i++)
			arr[i] = getSound( s[i] );
		return arr;
    }
    public static Music[] getMusic(String[] s) {
        Music[] arr = new Music[s.length];
		for(int i=0;i<s.length;i++)
			arr[i] = getMusic( s[i] );
		return arr;
    }

	public static Image getTiledImage(String smallImg,int width,int height) {
		// 8888888
		return null;
	}

	public static AudioClip[] getAudioClip(String[] str) {
		// 8888888
		return null;
	}
	public static void main(String[] args) {
        //System.out.println("Test Driver for testing this Module...");
        //getResource(null);
    }
	
	public static InputStream getResourceAsStream(String s) {
		return Application.getResourceAsStream(s);
	}
	
	public static RoomData getRoomData(String roomFile) {
		//System.out.println("Reached getRoomData("+roomFile+")" );
		RoomData retRD = new RoomData();
		Vector<String> vecStr = new Vector<String>();
		String curLine;
		try {
		//System.out.println("Inside getRoomData - Before getResourceAsStream()");
			InputStream is = Application.getResourceAsStream( rootDir + roomFile);
			//System.out.println("Inside getRoomData -  After getResourceAsStream()");
			if( is == null ) {
				throw new RuntimeException(" Unable to find ("+roomFile+"). Pls check if file exists." );
			}
			BufferedReader in = new BufferedReader( new InputStreamReader(is) );  
			if( in == null) {
				throw new RuntimeException(" Unable to read ("+roomFile+"). Pls check if file exists and check its contents." );
			}
			if (( curLine = in.readLine()) == null) { throw new RuntimeException("Incorrect format of Room file \""+ roomFile + "\" at Line(1)"); }				parseRoomData(1,curLine,retRD);
			
			if (( curLine = in.readLine()) == null) { throw new RuntimeException("Incorrect format of Room file \""+ roomFile + "\" at Line(2)"); }				parseRoomData(2,curLine,retRD);
			
			while (( curLine = in.readLine()) != null) {
			
				int index = curLine.indexOf('#');
				String part1 = curLine.substring( 0 , index);
				String part2 = curLine.substring(index+1, curLine.length() );
				part1 = part1.trim();  part2 = part2.trim();
				int index2 = part2.indexOf('#');
				String spriteName = part2.substring( 0 , index2);
				spriteName = spriteName.trim();
				
				String[] parts = part1.split(",");
				if ( parts.length < 3 ) { throw new RuntimeException(" Invalid data in room file"); }
				int spriteX = Integer.parseInt( parts[0] );
				int spriteY = Integer.parseInt( parts[1] );
				int spritePos = Integer.parseInt( parts[2] );

				String spawnString = String.format( part2, spriteX, spriteY );
				//System.out.print ( spawnString + "|");
				vecStr.add(spawnString);
			} 		
			in.close();
			is.close();
			// Copy vecStr to retRD
		} catch (FileNotFoundException e) {   e.printStackTrace();		} 
		catch ( IOException ioe ) {   System.out.println( ioe.getMessage() ); 	}
		
		retRD.spawnStrings = vecStr.toArray( new String[0] );		
		return retRD;
	} // end of method getRoom


				
        /*}
        catch (java.net.URISyntaxException e) {
           e.printStackTrace();
        }*/
	public static void parseRoomData(int lineNo, String parseStr, RoomData rd) {
		if( lineNo ==1 ) {
			String[] parts = parseStr.split(","); 
			
			if( parts.length < 2) { 
				throw new RuntimeException("Incorrect format of room file - Line #1 Check if Room width and height is present"); 
			}
			try {
			
			rd.width = Integer.parseInt(parts[0].trim());
			rd.height = Integer.parseInt(parts[1].trim());
			} catch( Exception e ) {
				throw new RuntimeException( e.getClass().getName() + ":" + e.getMessage() + "Incorrect format of room file - Line #1 Check if Room width and height are integers ");
			}
			if( parts.length >=3 ) {
				rd.bkImageFile = parts[2].trim();
				if( rd.bkImageFile != null && rd.bkImageFile.length() > 0 ) {
					try {   rd.img = ResourceLoader.getImage( rd.bkImageFile ); 	}
					catch( Exception e ) { 
						System.out.println("Unable to getImage("+rd.bkImageFile+"); Using default Room Image");  
						rd.img = Room.defaultImg;
					}
					if( rd.img != Room.defaultImg ) {
						if( rd.width != rd.img.getWidth(null) || rd.height != rd.img.getHeight(null) ) {
							throw new RuntimeException("RoomData's width,height("+rd.width+","+rd.height+
							") does not match its image's width,height("+rd.bkImageFile+":"+rd.img.getWidth(null)+","+rd.img.getHeight(null)+")");
						}
					}
				}				
			}
		}
		if ( lineNo ==2 ) {
		
			String[] parts = parseStr.split(","); 
			//System.out.println("Entered parseRoomData(2,just after split)");	
			rd.vx = toInteger(parts, 0 , 0);
			rd.vy = toInteger(parts, 1 , 0);
			rd.vw = toInteger(parts, 2 , rd.width);
			rd.vh = toInteger(parts, 3 , rd.height);

			rd.vl = toInteger(parts, 4 , rd.vw/4);
			rd.vr = toInteger(parts, 5 , rd.vw/4);
			rd.vu = toInteger(parts, 6 , rd.vh/4);
			rd.vd = toInteger(parts, 7 , rd.vh/4);

			//System.out.println("Exited parseRoomData(2,quitting method)");	
		}	
	}
	// to parse line #2 of the roomdata.
	public static int toInteger(String[] str,int pos,int defaultValue) {
		int retValue = defaultValue;
		if( str != null && str.length > pos && str[pos] != null && str[pos].trim().length() != 0 ) {
			try {
				//System.out.println("About to do parseInt");
				retValue = 	Integer.parseInt(str[pos].trim());
			} catch( Exception e ) {
					throw new RuntimeException( e.getClass().getName() + ":" + e.getMessage() + 
					"Incorrect format of room file - Line #2 Field #"+ (pos+1) + " Check xml to see if non integer value is specified. ");
			}
		} 
		return retValue;
	}
	
}   // end of class ResourceLoader

// Required to read data from XML files
class XMLReader {

    static Vector<NodeInfo> getNodeInfo(String xmlFile,String tag, String leafNode) {
        Vector<NodeInfo> retVec = new Vector<NodeInfo>();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
           DocumentBuilder builder = factory.newDocumentBuilder();
           // Replaced new code
			 //System.out.println("filename = \"" + xmlFile + "\"");		   
             URL url = Application.classLoader.getResource(xmlFile);
			 //System.out.println("url = \"" + url + "\"");
/* 
FYI - Note the url will look different when this is run as a jar. (See below the url uses jar protocol)
Eg. 
xmlFile = "tankattack/sprites/tankattack.sprites.Explosion/tankattack.sprites.Explosion.xml"
When app is run as a jar - 
url="jar:file:/C:/Documents%20and%20Settings/sbhat2/Desktop/pers/gamepack/cur/jars/tankAttack.jar!/tankattack/sprites/tankattack.sprites.Explosion/tankattack.sprites.Explosion.xml" 
When app is run normally -
url="file:/C:/Documents%20and%20Settings/sbhat2/Desktop/pers/gamepack/cur/classes/tankattack/sprites/tankattack.sprites.Explosion/tankattack.sprites.Explosion.xml"
*/
			 
			 if( url == null ) 
				throw new RuntimeException("XMLReader - Unable to find ("+xmlFile+"). Pls check if file exists." );
             String uriString = url.toURI().toString();			 
             Document document = builder.parse( uriString );
			 
           /* Existing old working code
             Document document = builder.parse( new File(xmlFile) );
           */
           Element root = document.getDocumentElement();
           Element target = getElement( root,tag );
           if( target == null )
             throw new RuntimeException("XMLReader - Unable to find tag (" + tag + ") in file (" + xmlFile + ")" );
           retVec = getData1( target, leafNode );
        }
        catch (java.net.URISyntaxException e) {
           e.printStackTrace();
        }
        catch (SAXException sxe) { // Error generated during parsing)
           Exception  x = sxe;
           if (sxe.getException() != null)
               x = sxe.getException();
           x.printStackTrace();
        } catch (ParserConfigurationException pce) { // Parser with specified options can't be built
            pce.printStackTrace();
        } catch (IOException ioe) {  // I/O error
           ioe.printStackTrace();
        }
        return retVec;
    }

    static public Vector<NodeInfo> getData1(Element elem, String leafNode ) {

       Vector<NodeInfo> retVec = new Vector<NodeInfo>();
       // Extra Defensive check may be removed later...
           if( elem == null )
              throw new RuntimeException("Got Null element");

           NodeList nlist = elem.getElementsByTagName(leafNode);
           if ( nlist == null ) {
              return retVec;
           }
           int numTags = nlist.getLength();
           //System.out.println( "elem = " + elem + " leafNode = " + leafNode + " numTags = " + numTags + " Typically this should be 1");
           for( int i=0;i<numTags;i++ ) {
                NodeInfo ni = new NodeInfo();
                Map<String,String> attribs = new HashMap<String,String>();
                String data = "";
                //----------------------------------------------------------
               Node n = nlist.item(i);

               // Finding the attribute value for Node n
               NamedNodeMap nnm = n.getAttributes();
                if( nnm != null && nnm.getLength() > 0 ) {
                   int numOfAttribs = nnm.getLength();
                   for( int k=0;k<numOfAttribs;k++) {
                        Node attrNode = nnm.item(k);
                        String atName = attrNode.getNodeName();
                        String atValue = attrNode.getNodeValue();
                        attribs.put( atName , atValue );
                        //System.out.println( " " +  atName + ":" + atValue );
                   }
                }
               // Finding the Data value for Node n
                 Node childNode = n.getFirstChild();
                 if( childNode != null ) {
                    data = childNode.getNodeValue();
                 }
               // -------------------------------------
               ni.attribs = attribs;
               ni.data = data;
               retVec.add( ni );
           } // end of for numtags
           return retVec;
    }

    /**
     *  Given a Parent Node and encoded name of childNode, this
     *  method retrieves the Child Node
     */

static public Element getElement(Element parent,String childElementStr) {

       // Extract Attribute Name/Value from child if present.
       // If ChildElementStr has ':' char then it is parsed
       // to get the attribute information.
       // Eg.
       // If childElementStr is "Sprite:name=Tank" then this
       // method should return DOM Element with Tag "Sprite"
       // which has attribute "name" set to "Tank"

       String attribName = null;
       String attribValue = null;
       String[] arr = childElementStr.split(":");
       if( arr.length > 1 ) {
            String attrib = arr[1];
            String[] arr2 = attrib.split("=");
            if( arr2.length != 2 ) {
               throw new RuntimeException("Parse Error - Invalid format for Child Node [" + childElementStr + "]");
            }
            attribName = arr2[0];
            attribValue = arr2[1];
        }
       String child = arr[0];
       Element childElement = null; // The element to be returned.

       NodeList nl = parent.getElementsByTagName(child);
       int numChildren = nl.getLength();
       if( numChildren == 0 )
           throw new RuntimeException("Node " + child + " not Found");
       //System.out.println( "Number of Nodes with " + child + " tag = " + numChildren );
       for(int i =0; i<numChildren; i++) {
             Node n = nl.item(i);
             if( attribName == null ) {
                 if( n != null && n.getNodeType() == Node.ELEMENT_NODE ) {
                    childElement = (Element) n;
                    break;
                 }
             }
             else {

               NamedNodeMap nnm = n.getAttributes();
               if( nnm != null && nnm.getLength() > 0 ) {
                 Node nn = nnm.getNamedItem(attribName);
                 if(nn != null && nn.getNodeValue().equals( attribValue ) ) {
                   if( n.getNodeType() == Node.ELEMENT_NODE ) {
                     childElement = (Element) n;
                     break;
                   }
                 }
               }
             }
        }
        return childElement;
    }// EOMethod
}   // End of class XMLReader

/**
 *  Reads XMLFile using XMLReader class. Then converts the data into
 *  Resource.
 *
 * Scratch Notes:
 * ResourceReader reads information from Sprite Resource Directory
 * (SRD in short). A SRD is a directory which contains all resources that
 *  may be needed by atleast one or more variety of Sprites. The resources
 * may be images like jpg gif or music files like wav/midi files.
 *
 * // Add a section about SRD format.
Simple Rule for a Sprite Resource Directory: (SRD) for short
1. The name of the xml file is the same as the name of the directory
2. All resources reffered to my xml are in same dir or nested dirs.

This makes it possible to merge multiple SRDs to a single SRD.

A Game developer may choose one of the following approaches
for managing game resources -
* Use a shared SRD for all Sprite resources
* Use multiple Sprite Specific SRDs one for each sprite/room etc within the game.
* Use a combination of both

If the developer chooses a single SRD, he can configure gampack
to use the single SRD. By default this SRD is named common.xml,
but this can be changed to any name of developer's choice.

If the developer chooses multiple SRDs, then he can place each Sprite's
SRD in the resouce directory.

The developer can also choose a combination of both approaches, by
places some loading some resouces from a single large SRD and others
by Sprite Specific SRD.

To understand this better let us look at the standard inteface provided
by gamepack API to load sprite resource.

ResourceLoader.loadSpriteResouce("tankattack.sprites.Tank");

By default gamepack will first search for the resource under
single large SRD - Resource/common.xml
It will look for the following tag
<Sprite name="tankattack.sprites.Tank"> to get the resources.

If it is unable to find this resource, then it will
look for a Sprite specific SRD by this name under resource directory -
Resource/tankattack.sprites.Tank

The search order may also be reversed using Gamepack API.


By default
/Resource/common/common.xml
/Resource/common/Images/
/Resource/common/Sound/

/Resource/tankattack.games.RoomA/tankattack.games.RoomA.xml
/Resource/tankattack.sprites.Tank/tankattack.sprites.Tank.xml

say these are some resources..
[rightp1.jpg]
[rightp2.jpg]
[images/blahdir/leftp1.jpg]

in case of Spr Specific SRD its is located at
/Resource/tankattack.sprites.Tank/tankattack.sprites.Tank.xml
/Resource/tankattack.sprites.Tank/rightp1.jpg
/Resource/tankattack.sprites.Tank/rightp2.jpg
/Resource/tankattack.sprites.Tank/images/blahdir/leftp1.jpg

in case of Single Large SRD it is located at
/Resource/Common/Common.xml
/Resource/Common/rightp1.jpg
/Resource/Common/rightp2.jpg
/Resource/Common/images/blahdir/leftp1.jpg
 *
 */

// Contains Information about a DOM Node in a easily accessible manner
class NodeInfo {
      // All attributes data for the DOM node is store here.
      public Map<String,String> attribs;
      // The Data within the DOM Node.
      public String data;

      public String toString() {
             String s = "Attribs" + attribs + " Data[" + data + "]";
             return s;
      }
}

